package com.siebre.picclife.asynctask.service.support;

import static com.google.common.collect.Iterables.tryFind;

import java.util.List;
import java.util.Map;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * 
 * @author ZhangChi
 * @since 2015年4月9日
 *
 */
public class TaskArgumentHandlerManager implements TaskArgumentHandlerRegistry, ArgumentSerializerResolver, ArgumentDeserializerResolver {

	private TaskTypeResolver taskTypeResolver = new TaskTypeResolver();
	
	private ArgumentTypeResolver argumentTypeResolver = new ArgumentTypeResolver();
	
	private PredicateResolver predicateResolver = new PredicateResolver();

	public void register(String taskType, 
			ArgumentSerializer serializer,
			ArgumentDeserializer deserializer) {
		taskTypeResolver.addMapping(taskType, serializer, deserializer);
	}

	public void register(Class<?> argumentType, 
			ArgumentSerializer serializer,
			ArgumentDeserializer deserializer) {
		argumentTypeResolver.addMapping(argumentType, serializer, deserializer);
	}

	public void register(final Predicate<Class<?>> argumentTypePredicate,
			ArgumentSerializer serializer, ArgumentDeserializer deserializer) {
		predicateResolver.add(new HandlerPair(new Predicate<TaskArgument>() {

			public boolean apply(TaskArgument input) {
				return argumentTypePredicate.apply(input.getArgumentType());
			}
		}, serializer, deserializer));
	}

	public Optional<ArgumentSerializer> resolve(String taskType, Object argument) {
		TaskArgument taskArgument = new TaskArgument(taskType, argument);

		Optional<HandlerPair> handlerPair = taskTypeResolver.resolve(taskArgument);
		if (!handlerPair.isPresent()) {
			handlerPair = argumentTypeResolver.resolve(taskArgument);
		}
		if (!handlerPair.isPresent()) {
			handlerPair = predicateResolver.resolve(taskArgument);
		}
		if (handlerPair.isPresent()) {
			taskTypeResolver.addMapping(taskType, handlerPair.get().serializer, handlerPair.get().deserializer);
		}
		return handlerPair.transform(HandlerPair.getSerializer);
	}
	
	public Optional<ArgumentDeserializer> resolve(String taskType, String argument, Class<?> argumentType) {
		TaskArgument taskArgument = new TaskArgument(taskType, argument, argumentType);
		
		Optional<HandlerPair> result = taskTypeResolver.resolve(taskArgument);
		if (!result.isPresent()) {
			result = argumentTypeResolver.resolve(taskArgument);
		}
		if (!result.isPresent()) {
			result = predicateResolver.resolve(taskArgument);
		}
		return result.transform(HandlerPair.getDeserializer);
	}

	static class TaskTypeResolver implements HandlerPairResolver {
		
		private Map<String, HandlerPair> mapping = Maps.newHashMap();

		public Optional<HandlerPair> resolve(TaskArgument argument) {
			return Optional.fromNullable(mapping.get(argument.getTaskType()));
		}

		void addMapping(String taskType, ArgumentSerializer serializer, ArgumentDeserializer deserializer) {
			mapping.put(taskType, new HandlerPair(serializer, deserializer));
		}
	}
	
	static class ArgumentTypeResolver implements HandlerPairResolver {
		
		private Map<Class<?>, HandlerPair> mapping = Maps.newHashMap();

		public Optional<HandlerPair> resolve(TaskArgument argument) {
			HandlerPair handlerPair = get(argument.getArgumentType());
			return Optional.fromNullable(handlerPair);
		}

		//TODO extract into a utility
		private HandlerPair get(final Class<? extends Object> argumentType) {
			HandlerPair handlerPair = mapping.get(argumentType);
			if (handlerPair == null) {
				handlerPair = tryFind(
						mapping.keySet(), 
						new Predicate<Class<?>>() {

							public boolean apply(Class<?> input) {
								return input.isAssignableFrom(argumentType);
							}
						})
						.transform(Functions.forMap(mapping)).orNull();
				if (handlerPair != null) {
					mapping.put(argumentType, handlerPair);
				}
			}
			return handlerPair;
		}

		void addMapping(Class<?> argumentType, ArgumentSerializer serializer, ArgumentDeserializer deserializer) {
			mapping.put(argumentType, new HandlerPair(serializer, deserializer));
		}
	}
	
	/**
	 * 
	 * @author ZhangChi
	 * @since 2015年4月14日
	 *
	 */
	static class PredicateResolver implements HandlerPairResolver {
		
		private List<HandlerPair> handlerPairs = Lists.newArrayList();

		public void add(HandlerPair handlerPair) {
			handlerPairs.add(handlerPair);
		}

		public Optional<HandlerPair> resolve(TaskArgument argument) {
			return FluentIterable
					.from(handlerPairs)
					.firstMatch(argument.asPredicate());
		}
	}
	
	static class TaskArgument {
		
		private final String taskType;
		
		private final Object argument;
		
		private final Class<?> argumentType;
		
		private TaskArgument(String taskType, Object argument) {
			this.taskType = taskType;
			this.argument = argument;
			argumentType = argument.getClass();
		}

		private TaskArgument(String taskType, String argument, Class<?> argumentType) {
			this.taskType = taskType;
			this.argument = argument;
			this.argumentType = argumentType;
		}

		public String getTaskType() {
			return taskType;
		}

		public Class<?> getArgumentType() {
			return argumentType;
		}

		Predicate<HandlerPair> asPredicate() {
			return new Predicate<HandlerPair>() {

				public boolean apply(HandlerPair input) {
					return input.apply(TaskArgument.this);
				}
			};
		}

		public Object getArgument() {
			return argument;
		}
	}

	private static class HandlerPair implements Predicate<TaskArgument> {

		static final Function<HandlerPair, ArgumentSerializer> getSerializer = new Function<HandlerPair, ArgumentSerializer>() {

			public ArgumentSerializer apply(HandlerPair input) {
				return input.serializer;
			}
		};
		
		static final Function<HandlerPair, ArgumentDeserializer> getDeserializer = new Function<HandlerPair, ArgumentDeserializer>() {

			public ArgumentDeserializer apply(HandlerPair input) {
				return input.deserializer;
			}
		};
		
		private final Predicate<TaskArgument> predicate;
		
		private final ArgumentSerializer serializer;
		
		private final ArgumentDeserializer deserializer;

		@Deprecated
		private HandlerPair(ArgumentSerializer serializer,
				ArgumentDeserializer deserializer) {
			this.serializer = serializer;
			this.deserializer = deserializer;
			predicate = Predicates.alwaysFalse();
		}

		private HandlerPair(Predicate<TaskArgument> predicate,
				ArgumentSerializer serializer, ArgumentDeserializer deserializer) {
			this.predicate = predicate;
			this.serializer = serializer;
			this.deserializer = deserializer;
		}

		public boolean apply(TaskArgument argument) {
			return predicate.apply(argument);
		}
		
	}
	
	private static interface HandlerPairResolver {
		
		Optional<HandlerPair> resolve(TaskArgument argument);
	}
}
